#ifndef I_RASTERIZER_H_
#define I_RASTERIZER_H_


#include <RasterizerPreReq.h>

#define MAX_VARYINGS 32

ATTRIBUTE_ALIGNED16(struct) Light
{
	Color4 Ambient;
	Color4 Diffuse;
	Color4 Specular;
	Color4 Position;
	double ConstantAttenuation;
	double LinearAttenuation;
	double QuadraticAttenuation;
};

ATTRIBUTE_ALIGNED16(struct) Surface {
	int width;
	int height;

	IplImage* colorBuffer;

#define OD255  (1.0f / 255.0f);

	inline Vec4 getNearest(double u, double v) const {
		Vec4 c(1,1,1,1);

		u -= (int)u;
		v -= (int)v;

		if ( u < 0.0) u += 1.0;
		if ( v < 0.0) v += 1.0;

		int x = (int)(u*width), y = (int)(v*height);

		uchar* data = (width*y+x) * 3 + (uchar*)colorBuffer->imageData;
		c.x = data[0];
		c.y = data[1];
		c.z = data[2];
		c*= OD255;

		return c;
	}

	inline Vec4 getBilinear(double s, double t)
	{
		Color4 col(1,1,1,1);

		s -= (int)s;
		t -= (int)t;
		
		if(s < 0.0) s += 1.0;
		if(t < 0.0) t += 1.0;

		double dx = s * width - 0.5, dy = t * height - 0.5;

		if(dx < 0.0) dx += width;
		if(dy < 0.0) dy += height;

		int x0 = (int)dx, y0 = (int)dy, x1 = (x0 + 1) % width, y1 = (y0 + 1) % height;

		int Width3 = width * 3;

		uchar *y0w = y0 * Width3 + (uchar*)colorBuffer->imageData;
		uchar *y1w = y1 * Width3 + (uchar*)colorBuffer->imageData;

		int x03 = x0 * 3, x13 = x1 * 3;

		uchar *a = y0w + x03;
		uchar *b = y0w + x13;
		uchar *c = y1w + x13;
		uchar *d = y1w + x03;

		double u1 = dx - x0, v1 = dy - y0, u0 = 1.0 - u1, v0 = 1.0 - v1;

		u0 *= OD255;
		u1 *= OD255;

		double u0v0 = u0 * v0, u1v0 = u1 * v0, u1v1 = u1 * v1, u0v1 = u0 * v1;

		col.x = u0v0 * a[0] + u1v0 * b[0] + u1v1 * c[0] + u0v1 * d[0];
		col.y = u0v0 * a[1] + u1v0 * b[1] + u1v1 * c[1] + u0v1 * d[1];
		col.z = u0v0 * a[2] + u1v0 * b[2] + u1v1 * c[2] + u0v1 * d[2];

		return col;
	}
};
typedef Surface Texture;


ATTRIBUTE_ALIGNED16(struct) Material
{
	Material(){}
	~Material(){}

	std::string		name;
	Texture*		texture;

	Vec4	ambient;
	Vec4	diffuse;
	Vec4	specular;
	double	reflect;
	double	refract;
	double	trans;
	double	shiny;
	double	glossy;
	double	refract_index;
};





ATTRIBUTE_ALIGNED16(struct) Vertex {
	Vec4	p;
	Color4	c;
	Vec4	n;
	Vec2	t;
};
ATTRIBUTE_ALIGNED16(struct) Face {
	Vertex v[3];
};

ATTRIBUTE_ALIGNED16(struct) Mesh {
	Mesh(){}
	~Mesh(){}

	std::vector<Face>	faces;
	std::vector<Vertex>	vertices;
	std::string			name;
	Mat4x4				transform;
	Material			material;

	void loadFromFile(const std::string& path);
	void writeToFile(const std::string& path);
};

struct RenderTarget {
	int width;
	int height;
	IplImage* colorBuffer;
	IplImage* depthBuffer;
};

ATTRIBUTE_ALIGNED16(struct) TransformedVertex {
	Vec4 p;

	Real varyings[MAX_VARYINGS];
};
ATTRIBUTE_ALIGNED16(struct) Fragment 
{
	Vec4 fragCoord;
	Real varyings[MAX_VARYINGS];
};

ATTRIBUTE_ALIGNED16(struct) ShaderGlobals {
	Mat4x4				world, view, proj;
	Mat4x4				worldViewProj;
	Mat4x4				viewInv;

	RenderTarget*		renderTarget;

	Material*			material;
	std::vector<Light>*	lights;
};

ATTRIBUTE_ALIGNED16(struct) VertexShaderParameters{
	const ShaderGlobals*	globals;
	const Material*			material;
	const Vertex*			in;
	TransformedVertex*		out;
};
ATTRIBUTE_ALIGNED16(struct) FragmentShaderParameters {
	const ShaderGlobals*	globals;
	Fragment*				fragment;
	Material*				material;
	Vec4					out;
};


typedef void (*VERTEXSHADER)(VertexShaderParameters *params);
typedef void (*FRAGMENTSHADER)(FragmentShaderParameters* params);


ATTRIBUTE_ALIGNED16(class) IRasterizer {
public:
	enum TRANSFORM {
		VIEW, WORLD, PROJECTION
	};
public:
	IRasterizer() {}
	virtual ~IRasterizer() {}

	virtual Mesh* addMesh(const std::string& name, objLoader& obj,  const Vec3& loc=Vec3(0,0,0), const Vec3& rot = Vec3(0,0,0), const Vec3& sca=Vec3(1,1,1)) = 0;
	virtual void beginMesh(const std::string& name) = 0;
	virtual void addVertex(const Vec4& p, const Color4& c, const Vec4& n, const Vec2& t) = 0;
	virtual void addVertex(const Vertex& vertex) = 0;
	virtual void addIndex(size_t idx) = 0;
	virtual Mesh* endMesh() = 0;
	virtual Texture* loadTexture(const std::string& path) = 0;

	virtual double render() = 0;//returns the time

	virtual RenderTarget* createRenderTarget(int width, int height) = 0;
	virtual void setRenderTarget(RenderTarget* rt) = 0;

	virtual void setVertexShader(VERTEXSHADER vs ) = 0;
	virtual void setFragmentShader(FRAGMENTSHADER fs) = 0;
	virtual void setTransform(TRANSFORM t, const Mat4x4& m) = 0;
};

#endif //I_RASTERIZER_H_